/**
This module defines constants and types to represent features of the
D programming language for, e.g., reflection.
 */
module ext.lang;

import std.typetuple;


/***************************************
A $(D Linkage) member value represents a linkage type.

Example:
--------------------
// The linkage type of printf is C.
extern(C) int printf(const char*, ...);
--------------------
 */
enum Linkage
{
    /**
     * Linkage types.
     */
    D,
    C,       /// ditto
    CPP,     /// ditto
    WINDOWS, /// ditto
    PASCAL,  /// ditto
}

/// Ditto
string toString(Linkage link) pure nothrow
out(s)
{
    assert(s != null);
}
body
{
    static immutable string[Linkage.max + 1] LOOKUP =
    [
        Linkage.      D:       "D",
        Linkage.      C:       "C",
        Linkage.    CPP:     "C++",
        Linkage.WINDOWS: "Windows",
        Linkage. PASCAL:  "Pascal",
    ];
    return LOOKUP[link];
}


/***************************************
A bitwise-OR of $(D StorageClass) member values represents a
type-constructing storage class and/or a parameter-storage class.

Example:
--------------------
alias StorageClass STC;

STC stc = STC.SHARED | STC.CONST | STC.REF;
// shared const is a type-constructing storage class.
assert((stc & STC.TYPECTOR) == STC.SHARED | STC.CONST);
// ref is a parameter storage class.
assert((stc & STC.PARAMETER) == STC.REF);
--------------------
 */
enum StorageClass : uint
{
    /**
     * No storage class.
     */
    NONE = 0,

    /**
     * Type-constructing storage classes.
     *
     * Note that there is no $(D shared immutable) nor
     * $(D const immutable) storage classes.
     */
    SHARED    = 1 << 0,
    CONST     = 1 << 1, /// ditto
    IMMUTABLE = 1 << 2, /// ditto

    /**
     * Parameter storage classes.
     */
    SCOPE = 1 << 3,
    IN    = 1 << 4, /// ditto
    OUT   = 1 << 5, /// ditto
    REF   = 1 << 6, /// ditto
    LAZY  = 1 << 7, /// ditto

    /**
     * A bit mask comprised of the type-constructing storage classes.
     */
    TYPECTOR = SHARED | CONST | IMMUTABLE,

    /**
     * A bit mask comprised of the parameter storage classes.
     */
    PARAMETER = SCOPE | IN | OUT | REF | LAZY,
}


/***************************************
A bitwise-OR of $(D FunctionAttribute) member values represents function
attributes such as $(D pure nothrow).
 */
enum FunctionAttribute : uint
{
    /**
     * No function attribute.
     */
    NONE = 0,

    /**
     * General function attributes.
     */
    PURE    = 1 << 0,
    NOTHROW = 1 << 1, /// ditto
    REF     = 1 << 2, /// ditto
}


/***************************************
A $(D Variadic) member value indicates the variadicity of a function,
i.e. whether the function has variadic parameter or not, and if so,
whether the parameter is typesafe or not.
 */
enum Variadic
{
    /// Not variadic.
    NO,
    /// $(D argptr)-based variadic function $(D ( T, U, ... )).
    VARIADIC,
    /// Typesafe variadic function $(D ( T, U[]... )).
    TYPESAFE,
}


/***************************************
An $(D OverloadSet) instance represents a set of overloaded symbols of
the same name.
 */
template OverloadSet(string name_, syms...)
{
    /**
     * The overloaded symbol _name.
     */
    enum string name = name_;

    /**
     * A tuple of overloaded symbols.
     */
    alias syms overloads;
}


// : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : //
// For other modules in this package
// : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : //

/* *************************************
Appends a type-constructing storage class and a parameter storage class
to a _type name.

The $(D shared const) storage class is coded to $(D shared(const(T)))
where $(D T) is the modified _type. The $(D scope) parameter storage
class follows any other parameter-storage class, as $(D lazy scope)
should be generated.
 */
package string appendStorageClass(string type, StorageClass stoc)
    pure nothrow
{
    alias StorageClass STC;
    string s = type;

    // type storage class (in this order for "shared(const(T))")
    if (stoc & STC.CONST    ) s =     "const(" ~ s ~ ")";
    if (stoc & STC.IMMUTABLE) s = "immutable(" ~ s ~ ")";
    if (stoc & STC.SHARED   ) s =    "shared(" ~ s ~ ")";

    // parameter storage class (in this order for "lazy scope T")
    if (stoc & STC.SCOPE) s = "scope " ~ s;
    if (stoc & STC.IN   ) s =    "in " ~ s;
    if (stoc & STC.OUT  ) s =   "out " ~ s;
    if (stoc & STC.REF  ) s =   "ref " ~ s;
    if (stoc & STC.LAZY ) s =  "lazy " ~ s;

    return s;
}

unittest
{
    alias StorageClass STC;

    const t1 = appendStorageClass("T", STC.NONE);
    assert(t1 == "T");

    const t21 = appendStorageClass("T", STC.SHARED);
    const t22 = appendStorageClass("T", STC.CONST);
    const t23 = appendStorageClass("T", STC.IMMUTABLE);
    const t24 = appendStorageClass("T", STC.SHARED | STC.CONST);
    assert(t21 == "shared(T)");
    assert(t22 == "const(T)");
    assert(t23 == "immutable(T)");
    assert(t24 == "shared(const(T))");

    const t31 = appendStorageClass("T", STC.SCOPE);
    const t32 = appendStorageClass("T", STC.IN);
    const t33 = appendStorageClass("T", STC.OUT);
    const t34 = appendStorageClass("T", STC.REF);
    const t35 = appendStorageClass("T", STC.LAZY);
    const t36 = appendStorageClass("T", STC.LAZY | STC.SCOPE);
    assert(t31 == "scope T");
    assert(t32 == "in T");
    assert(t33 == "out T");
    assert(t34 == "ref T");
    assert(t35 == "lazy T");
    assert(t36 == "lazy scope T");

    const t4 = appendStorageClass("T", STC.CONST | STC.REF);
    assert(t4 == "ref const(T)");
}


/* *************************************
Appends an ellipsis $(D ...) as a parameter which takes variadic
arguments at the end of a function parameter declarations.

Params:
  params = A string representing function parameter declarations.
  var    = Whether or not to append the ellipsis.
 */
package string appendVararg(string params, Variadic var) pure nothrow
{
    string s = params;

    switch (var)
    {
        case Variadic.NO:
            break;

        case Variadic.VARIADIC:
            if (s.length)
                s ~= ", ...";
            else
                s  =   "..."; // in the case of func(...)
            break;

        case Variadic.TYPESAFE:
            s ~= "...";
            break;

        default: assert(0);
    }
    return s;
}

unittest
{
    assert(appendVararg("int", Variadic.NO) == "int");
    assert(appendVararg("int", Variadic.VARIADIC) == "int, ...");
    assert(appendVararg("", Variadic.VARIADIC) == "...");
    assert(appendVararg("int[]", Variadic.TYPESAFE) == "int[]...");
}


/* *************************************
Appends some attributes to the beginning of a function signature. The
attributes consist of a linkage (calling convention) followed by a
storage class and then function attributes.

Example:
--------------------
string sig = "int foo()";
string fun = sig.prefixFunction(
    Linkage.C, StorageClass.CONST, FunctionAttribute.NOTHROW);
assert(fun == "extern(C) const nothrow int foo()");
--------------------
 */
package string prefixFunction(
        string func, Linkage conv, StorageClass stc,
        FunctionAttribute atts) pure nothrow
{
    alias      StorageClass STC;
    alias FunctionAttribute FA;

    string prefix = "";

    if (conv != Linkage.init)
        prefix ~= "extern(" ~ toString(conv) ~ ") ";

    /+
    if (atts & FA.STATIC  ) prefix ~=   "static ";
    if (atts & FA.ABSTRACT) prefix ~= "abstract ";
    if (atts & FA.FINAL   ) prefix ~=    "final ";
    +/

    if (stc & STC.SHARED   ) prefix ~=    "shared ";
    if (stc & STC.CONST    ) prefix ~=     "const ";
    if (stc & STC.IMMUTABLE) prefix ~= "immutable ";

    if (atts & FA.PURE   ) prefix ~=    "pure ";
    if (atts & FA.NOTHROW) prefix ~= "nothrow ";
    if (atts & FA.REF    ) prefix ~=     "ref ";

    return prefix ~ func;
}

unittest
{
    alias           Linkage L;
    alias      StorageClass STC;
    alias FunctionAttribute FA;

    const func = "int foo()";

    const sig11 = func.prefixFunction(L.D, STC.NONE, FA.NONE);
    const sig12 = func.prefixFunction(L.C, STC.NONE, FA.NONE);
    const sig13 = func.prefixFunction(L.CPP, STC.NONE, FA.NONE);
    assert(sig11 == "int foo()");
    assert(sig12 == "extern(C) int foo()");
    assert(sig13 == "extern(C++) int foo()");

    const sig21 = func.prefixFunction(L.D, STC.SHARED, FA.NONE);
    const sig22 = func.prefixFunction(L.D, STC.CONST, FA.NONE);
    const sig23 = func.prefixFunction(L.D, STC.IMMUTABLE, FA.NONE);
    const sig24 = func.prefixFunction(
            L.D, STC.SHARED | STC.CONST, FA.NONE);
    assert(sig21 == "shared int foo()");
    assert(sig22 == "const int foo()");
    assert(sig23 == "immutable int foo()");
    assert(sig24 == "shared const int foo()");

    const sig31 = func.prefixFunction(L.D, STC.NONE, FA.PURE);
    const sig32 = func.prefixFunction(L.D, STC.NONE, FA.NOTHROW);
    const sig33 = func.prefixFunction(L.D, STC.NONE, FA.REF);
    const sig34 = func.prefixFunction(
            L.D, STC.NONE, FA.PURE | FA.NOTHROW | FA.REF);
    assert(sig31 == "pure int foo()");
    assert(sig32 == "nothrow int foo()");
    assert(sig33 == "ref int foo()");
    assert(sig34 == "pure nothrow ref int foo()");

    /+
    const sig41 = func.prefixFunction(L.D, STC.NONE, FA.STATIC);
    const sig42 = func.prefixFunction(L.D, STC.NONE, FA.ABSTRACT);
    const sig43 = func.prefixFunction(L.D, STC.NONE, FA.FINAL);
    assert(sig41 == "static int foo()");
    assert(sig42 == "abstract int foo()");
    assert(sig43 == "final int foo()");
    +/

    const sig5 = func.prefixFunction(
            L.C, STC.CONST, FA.NOTHROW/+ | FA.FINAL+/);
    assert(sig5 == "extern(C) final const nothrow int foo()");
}


